Skip navigation and go to content

Structure Markup for Programmatic Accessibility

On this page

The Goal of Programmatic Accessibility

The goal of creating programmatic accessibility information is to create machine-readable relationships so they can be exposed to screen readers and other Assistive Technologies. We saw this in the lesson on accessible names for forms, where our markup set a label to be used as the name for a search input.

If we structure components with proper semantic elements, programmatic accessibility information can be determined from the Document Object Model (DOM) and platform accessibility APIs.

Let’s head back to the Listing Detail page and take another look at the date picker. This challenge can get a bit code-heavy. But you’ll have a solution you can reuse by the end of it.

A div with CSS Grid is what styles the date buttons.

The date buttons are laid out with CSS Grid that has been applied to a div. While visually the calendar looks structured, there’s no accessibility information that backs it up. Each individual button has a role and a name, but there’s nothing wrapping the buttons to communicate a programmatic calendar structure.

Refactoring the date picker to be marked up as a semantic HTML table instead of a div would indicate to Assistive Technology that the rows of dates correspond to columns for the days of the week. That’s exactly what we’re going to do.

Reviewing the Date Picker’s Current Implementation

Let’s refresh our memory of how the date picker works by reading its source code at components/date-picker/date-picker.js.

A days array is created thanks to some utility functions that use Date.js. The array contains 35 objects, each with a date and some additional data used for styling & booking functionality.

Console log shows 35 days logged as objects in an array

The days array is mapped over to create each individual button, along with the ARIA we added earlier.

// components/date-picker/date-picker.js

<div className="date-grid">
  {days.map((day, index) => {
    return <button
				...

The div with a class of date-grid that wraps the buttons uses CSS Grid to create the seven styled columns for each day of the week:

// excerpt from date-picker.scss
div.date-grid {
    display: grid;
    gap: 0.25em;
    grid-template-columns: repeat(7, 1fr);
}

Granted, it is really cool that we can use CSS Grid to nicely create a calendar! But in this case it doesn’t include the accessibility information that it should.

Video: Intro to Programmatic Accessibility
Loaded: 2%
Current Time 0:00
/
Duration Time 3:20
Video Transcript

Let's talk about more programmatic accessibility information with HTML and aria. So the goal of creating programmatic accessibility information, and we've seen some already using things like aria labeled by. But the goal of it is to create relationships that are machine readable so that our document object model has accessibility information that can be exposed in screen readers.

And a lot of the stuff you know, some of the programmatic information can be determined from using Dom structure itself. Tables which we're actually going to take our date picker and convert it from a div grid. So if we inspect this, our date picker right now use a CSS grid hover over that. And so there's not a lot of programmatic accessibility information that indicates that this has.

Grid structure, like CSS is changing the display of it, but there's no kind of table rows and columns to indicate that this has tabular data. Whereas if we had a table that would programmatically, you know, from the semantics of the table would indicate to assistive technology that there are rows and columns and that they have meaning in this case that they've, they fall under.

Days of the week. So we're going to, for this section, we're going to add more programmatic accessibility information by converting this from a CSS grid into an actual table grid for tabular data. So let's, we've inspected our date picker quite a bit. This is going to be probably some of the heaviest code that we're going to write in this workshop is converting this from a CSS grid into a table.

And it's, it is a little bit challenging when you're in this dynamic react space because we are iterating over days. Like we have an array of days and I could even show you. Like if I do console log days, I'm going to, I'm going to do that outside of this. So I only get it once. I'll say console log days.

So days are, this is created up here in this logic part of our date picker. I'm going to hit save and show you what the structure looks like. So this is an array of objects. And when we're converting something from, you know, a nice, neat CSS grid where everything, you know, it's just one big old list and we're letting CSS handle the structure for us.

That's really only affecting the visual display. If we need to create something like a table. We have to kind of break this apart into sets of seven days for our calendar, which will have seven days showing. So that's going to take a bit more logic, which is why I say this will probably be some of the heaviest code that we write in this workshop.

That's okay. I got you. We're gonna, we're going to do this. And the goal is to create more of this programmatic association. So that screen readers can benefit from understanding that there's relationships in this structure where CSS grid alone. Doesn't tell us that like that's CSS grid is awesome. I love it.

We use it for a lot of things. But it doesn't give us any kind of semantic relationships or accessibility information. So let's, let's do that work. Let's come in here. We've seen what days look like. And so what we need to do is create kind of a structure that we can iterate over that is more of a table structure.

🛠️ Challenge: Convert the Date Picker to Use a Table for Programmatic Accessibility

Your challenge is to convert the date picker from a div element to using an HTML data table that builds in additional accessibility information.

Here is some guidance on how to complete this challenge:

The head of the table should be a row that sets up columns for each day of the week.

Use the array of days to create an array of weeks that can then be mapped over to populate the table’s body.

💡Tip

The MDN docs for the table element are a useful reference for this challenge.

As you work through the challenge, think about how accessible names could be used to add additional information:

  • Will the table headers for the days of the week make sense in a screen reader?
  • How might you expose more descriptive names for these cells?

🛠️ Solution: Convert the Date Picker to Use a Table for Programmatic Accessibility

While a div element using CSS Grid acts as one big container for everything inside of it, an HTML table requires a more carefully laid out structure with specific elements to create semantic rows and columns.

For our date picker calendar, each row will correspond to a week containing 7 days and each column will correspond to a day of the week. There will be a row of table header cells to mark the days of the week, while the rest of the table body will contain cells for the day buttons.

Create a Header with Days of the Week

We’ll start our conversion by wrapping the div that contains the days of the week with a <table> tag.

Since the days of the week will be the header for the the table, we’ll wrap them in a thead element and convert the div into a table row by replacing it with tr:

// components/date-picker/date-picker.js
<table>
	<thead>
		<tr className="days-of-week">
		  <span title="Sunday">S</span>
		  <span title="Monday">M</span>
		  <span title="Tuesday">T</span>
		  <span title="Wednesday">W</span>
		  <span title="Thursday">T</span>
		  <span title="Friday">F</span>
		  <span title="Saturday">S</span>
		</tr>
	</thead>
</table>

The spans for each day should be wrapped inside of table header elements. We’ll specify a scope attribute of col for column since each day of the week has a relationship to the dates shown vertically underneath it.

// components/date-picker/date-picker.js
<table>
	<thead>
		<tr className="days-of-week">
		  <th scope="col">
        <span title="Sunday">S</span>
      </th>
      <th scope="col">
        <span title="Monday">M</span>
      </th>
      <th scope="col">
        <span title="Tuesday">T</span>
      </th>
      <th scope="col">
        <span title="Wednesday">W</span>
      </th>
      <th scope="col">
        <span title="Thursday">T</span>
      </th>
      <th scope="col">
        <span title="Friday">F</span>
      </th>
      <th scope="col">
        <span title="Saturday">S</span>
      </th>
		</tr>
	</thead>
</table>

💡Tip

In VSCode for Mac, highlighting the repeated text you want to edit and hitting CMD+D will allow you to edit multiple instances at once.

Create an Array of Weeks

To create a dynamic HTML table as opposed to one big div container of dates, we need a new array structure for our source data that breaks the dates down by week. Therefore, an array of weeks where each entry contains the days for that week would allow us to create the right semantic structure.

There are multiple ways to create an array of weeks out of the days array. This is how we’ll accomplish it.

We’ll create a tableRows JavaScript function that will take in the array of days, a slice size (which will be 7 for the days of the week), and a callback function that we’ll call when we map over each row:

const tableRows = (daysArray, sliceSize, sliceFunc) => {
	
}

To populate the array of weeks, we’ll write a for loop to iterate over the passed in daysArray and use the Array slice method to create a subset of days that matches the specified slice size and push it into an array. When the loop finishes each iteration, the index will be increased by sliceSize to move 7 days ahead for the next week.

At the end of the function, we’ll return the new weeks array.

Here’s what it looks like all together:

// components/date-picker/date-picker.js

const tableRows = (daysArray, sliceSize, sliceFunc) => {
	const weeks = []
	
	for (var i=0; i < daysArray.length; i += sliceSize) {
		const slice = daysArray.slice(i, i + sliceSize)
		weeks.push(sliceFunc(slice, i))
	}
	return weeks
}

Update the Date Button Creation

The days are going to be a challenge because we need to stop every seven days to create a new table row. We can't just iterate over the whole array of dates in a div anymore.

Now that we are working with a data array of weeks, we need to rework the way our date buttons are created.

Below the <thead> inside our table we’ll add a <tbody> that will contain the call to our tableRows function. We’ll pass in the original days array, the slice size of 7 for the length of a week, and a callback function that will result in creating a table row that contains cells for each date button in that week. The <tr> can have the date-grid CSS class added to it. We’ll also use the dynamic week number for a key.

Here’s how the code looks so far:

// components/date-picker/date-picker.js
<tbody>
  {tableRows(days, 7, (week, weekNum) => (
		<tr className="date-grid" key={weekNum}>
				
			
		</tr>
	))}
</tbody>

💡Tip

React and JSX require a unique key value when rendering an array of items.

Now that we have laid the groundwork for creating the rows of the table from the individual weeks, we need to map over the individual days of the week to create the buttons.

This will look similar to our original div-based solution, except this time we’ll wrap each button in a <td> instead. The buttons themselves will be the same, so we can paste that code in:

<tbody>
  {tableRows(days, 7, (week, weekNum) => (
		<tr className="date-grid" key={weekNum}>
			{week.map((day, index) => (
				<td key={index}>
					<button
			      aria-label={
			        `${dayjs(day.date).format('MMMM D')}${day.isBooked ? ' already booked' : '' }${isDaySelected(day) ? ' selected' : ''}`
			      }
			      aria-disabled={day.isBooked ? 'true' : 'false'}
			      aria-selected={
			          isDaySelected(day) ? 'true' : 'false' 
			      }
			      className={[
			          'grid-btn',
			          day.isBooked ? 'booked' : '',
			          day.isCurrentMonth ? 'currentMonth' : '',
			          isDaySelected(day) ? 'selected' : ''
			      ].join(' ').trim()}
			      key={index}
			      onClick={() => selectDay(day)}
			    >
			      <time date-time={day.date}>{day.dayOfMonth}</time>
			      <span className="icon" aria-hidden="true"></span>
			    </button>
				</td>
			))}
		</tr>
	))}
</tbody>

Now the date picker is based on a table instead of divs!

The table head represents the days of the week for each column. Inside the table body we have table rows that contain cells, and each cell contains a date button.

Our date picker now has a more programmatic structure. The relationships between the columns and rows is communicated through Assistive Technology in a way that a generic div with CSS Grid just can’t do.

Video: Convert the Grid of divs to an HTML Table
Loaded: 1%
Current Time 0:00
/
Duration Time 8:12
Video Transcript

So for this. I need to come in here and change some of the way that this is structured. So first of all, we're going to change our date grid from a series of divs into a table starting with the days of the week. So the days of the week, right now, it's a div with a series of spans. That would be a great candidate for table headers in a table element.

So I'm going to kind of change some of this code from divs I'm going to put a table and I'm going to start by just wrapping it around the days of the week. And we're kind of, we're going to convert these from divs and spans into table headers. So within the table, we're going to need a thead, and a tbody

so T head will contain these table headers. The T head for days of the week, those are all going to be in one table row. They sit next to each other from left to right. So we're going to change that to tr and that can keep its class name of days of the week. So for each of these spans, those are going to go into table header cells.

So each one of these, I am going to select all of them at once using command D all of the opening span tags. That way I can change these to th, and they're going to have a scope of columns. So the table row goes left to right across and the scope of them is a column. So we can say scope equals call for a table header cell.

I can close this table header, I'm gonna drop this down a line. And now we have opening th spans and inside of each of these. Now we have, so table head table, row for, for each of these days. And they are marked up as table headers with the scope going down the column. So that wasn't too hard. We, we didn't have to introduce any logic.

We've got our table head it's the days that are going to be the challenge. And that's because we need to stop every seven days. We can't just iterate over the whole array anymore. So table. The closing table tag is going to come after our date grid. So I'm going to collapse this for a second. I'll close our table after this.

This is going to be kind of a mess until we finish it, but we need to change our date grid. Now it is going to be wrapped in a t body. So we've got thead and T body. And we had this like really straightforward date grid, converting it to a table in a more dynamic space is a little bit of a challenge. So we're going to add a function where we had this days.

That was all of the days. We need to create a new function here to give us more structure that we can iterate over, to create these weeks for each of the rows of our table. So I'm going to create a new function. Called table rows, and you're free to copy this. If you ever come up against this again. You are totally welcome to copy this code.

So I'm going to take the days array. We saw that in our console, we had that array of objects for all of the days. That's what we're going to start from. So we have days array. We're going to create a slice that is of a certain size. So say seven days for each slice of that array. We want each seven days, and then we're going to have a callback function that we call.

So the slice function. And this will be our iterator function that, you know, for each seven days, you know, each row of the table, we're going to call this. This is going to create a new array called weeks. So an empty array called weeks, and we're going to iterate over the days array. So for a variable, I equals zero.

Start our counter off with I equals zero. We'll say if I is less than days, array.Length. We can increment, so I plus equals slice size. So for each of these weeks, and within this iterator, I'm going to grab a slice. So const slice equals days, array dot slice put pass in our iterator here. And I plus slice size.

So for each of these weeks, we're going to capture a slice of the day's array and increment our variable. And then I'm going to push that into our weeks or array. Yeah. We're creating a new array with our weeks. We need a, we need a different structure than our big old list of days. So for weeks I can push this slice.

And call our callback function. So slice and the, the eye iterator and then table rows is going to return weeks. So that's the magic code to create slices of our days, array that then we have control over weeks and we can iterate over those, when I scroll back down here under our table now, instead of our date grid iterating over days, we are going to iterate over table rows.

So I am going to collapse our days.Map I'll actually just comment out this code for the time. And within T body I'm going to call. So in JSX I've got this T body, which is native raw HTML. I can call Java script by starting a conditional. So in this curly brace space, I can call table rows. We're going to pass in the days array.

So that's our, our, our days array option and our function that we. So days the slice size will be seven for the length of a week. And then we are going to pass in a function that will be our slice function. So this is our callback function that we go run that logic, you know, seven times. And for each one of those weeks, this is the function that we're going to call.

So we're going to give it a week, give it a week number, cause that can be useful. And then. So this is our function that we're going to call. We need opening, opening, parentheses, opening, parentheses, close paren closed paren closed curly. That should be good. I think it's just mad cause there's nothing happening inside.

So this is where we're going to create a table row and we can give it a class name of date grid. So. We're going to let each table row be a grid, but we're not going to let CSS grid handle all of the columns as well. We're actually creating those kind of manually by creating this table markup. So letting the table be our underlying semantic information, that CSS grid was not really supplying.

That was just affecting the visual display. So within date grid, Because we're iterating and react and it'd give us a key. That's where weekNum, comes in. Give me that we're passing in that iterator from our, our table rows function. I can use that to give an, a unique key. So react is happy within table row.

That's where I'm going to iterate over week. So table rose is giving us a handle on each week as we cycle through the slices of the array. Now I have this nested structure that I can map over. So I'm going to say week dot map. That's an array. So I can call a array.Map on it for each day. Within that week, I have a day and then the index.

That's to give me another key handle, kind of like we just saw it on our week. So week.Map and that has a callback function that is going to be open parentheses. And so within this is where we can create a TD for a table cell and TD needs that key for index. So within table key. Or table cell td

now we can do something like. Our buttons. So now we're at that individual table cell level. We had to do a bit of magic here to get our weeks. And so that was the code that it took. I just pasted our, our button code that we had before. Everything else should be the same. It's really just this kind of table structure around all of our buttons that we needed.

Our button code inside of it is still the same. All our other functions are still the same. All the labels, all of that stuff is still good. It was just the, the date grid and the fact that we were going over a whole list of days that was not relevant anymore. So I'm going to delete the code. We're not using clean this up a little bit and I'm going to hit save.

That is a bunch of magic.

Label an HTML Table with aria-labelledby

We can do a bit of labeling that will expose even more semantic details in Assistive Technology.

To expose additional accessibility information about the table, let’s use aria-labelledby.

💡Tip

The table caption element could also be used to add information, as long as it is visible and the first descendant of the table. Be sure to test table markup with Assistive Technologies!

The table’s heading for the current month is a good candidate for providing more information.

For the <h4> in the date picker’s header that has the current month, we’ll add an id of “month”.

Then for the table of dates, we’ll add an aria-labelledby attribute to the date table and point it to the month.

<header>
		...
    <h4 id="month">{ dayjs(activeDate).format("MMMM YYYY") }</h4>
		...
</header>
<table aria-labelledby="month">
 ...
</table>

With id set to month on our h4 and referenced by aria-labelledby, our table will have an accessible name that reflects the month currently displayed.

Video: Label an HTML Table with aria-labelledby
Loaded: 100%
Current Time 0:00
/
Duration Time 1:56
Video Transcript

Let's go see if it worked. I'm going to come over to the browser. And I'll hit refresh that has gotta be the smoothest transition ever gives to tables. So I'm going to inspect, let's come and look at this. So, because of the way the CSS was written for these date grids now we have CSS grid or date grid matching.

For each one of these. And I think date grid was the way that the CSS has written, it just seamlessly worked. And so a bit of magic there, but our structure is looking really good. Now we've got a table. The table head is representing the, the days of the week. And the table body. Now we have table rows that contain each of these cells and a cell contains a button.

And so we're really using a table to create more programmatic structure. So that way, like these relationships are actually communicated in the underlying markup where CSS grid just didn't have that power. It could be. And so that's working really well. There is a bit of labeling that we could do to really expose some of the semantic information in assistive technology.

And that includes exposing some labeling. So like for the table itself, we could label it like this table is for the month of July. So, we've got. The the month it's going add a label so that this table has either table captions. So table caption has to be visible. That's doesn't really fit our design.

So what I'm gonna do instead is add up here. I'm going to say ID equals months and that way this table can reference it. I could say ARIA dash labeled by. And point it to this month ID up here so that this table will be referenced for July, 2022. And when I hit save, I'm going to come over here to safari and we'll test this out in voiceover.

With the amount of work we just did, we should check it in our screen readers!